package gw.com.android.ui.me_concern;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

import java.util.ArrayList;
import java.util.List;

/**
 * 在这里，滑动的实际上是viewGroup的内容,利用scrollTo函数进行内容平移的
 */
public class MyConcernHorizontalSlideLayout extends ViewGroup {


    public MyConcernHorizontalSlideLayout(Context context) {
        this(context, null);
    }

    public MyConcernHorizontalSlideLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyConcernHorizontalSlideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
        mScroller = new Scroller(context);
    }

    private void init(Context context) {
//        setClickable(true);
        mScroller = new Scroller(context);
        mViewConfiguration = ViewConfiguration.get(context);
    }

    public View getmCenterView() {
        return mCenterView;
    }

    View mCenterView;//主布局全局变量

    public List<View> getMenuViewItemList() {
        return menuViewGroup;
    }

    List<View> menuViewGroup;// 用这个来标记Menu

    public int getmMaxScrollDistance() {
        return mMaxScrollDistance;
    }

    private int mMaxScrollDistance;//最大滑动距离
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker = null;//速度追踪器
    private ViewConfiguration mViewConfiguration;

    public boolean isIfAllowSwip() {
        return ifAllowSwip;
    }

    public void setIfAllowSwip(boolean ifAllowSwip) {
        this.ifAllowSwip = ifAllowSwip;
    }

    private boolean ifAllowSwip = true;
    private static boolean ifScrolling = false;

    /**
     * 测量。测量所有子控件的宽高. 此方法执行之后，这个ViewGroup之内的所有子控件宽度高度都将会确定下来。
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);//宽度-测量模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//宽度-测量大小
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);//高度-测量模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);//高度-测量大小

        mCenterView = null;// 让主布局初始化为空
        menuViewGroup = new ArrayList<>();
        mMaxScrollDistance = 0;//初始化为0

        int childCount = getChildCount();// 子控件的个数
        for (int i = 0; i < childCount; i++) {// 循环遍历子控件
            View child = getChildAt(i);//取得子控件对象
            if (child.getVisibility() == GONE) {//忽略隐藏并且不占空间的子
                continue;
            }
            int childWidth;
            if (mCenterView == null) {//排在第一位的子控件就作为主布局
                mCenterView = child;
                childWidth = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);//主布局，就把整个viewGroup的宽度，作为它的宽度
            } else {
                menuViewGroup.add(child);//
                childWidth = heightMeasureSpec;//否则，就将viewGroup的高度值，设定为子的宽度（这是为了让其余的子控件呈现为正方形）
                mMaxScrollDistance += MeasureSpec.getSize(childWidth);//同时累加计算最大滑动距离,最大滚动距离就是所有菜单item的宽度的和
            }
            child.measure(childWidth, heightMeasureSpec);// 设置每一个子控件的宽高
        }
        setMeasuredDimension(widthSize, heightSize);//
        //虽然，每一个控件的宽高都已经测量完成了，但是还没有进行排布，请看onLayout
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        int childCount = getChildCount();
        int offsetX = 0;// X偏移量
        for (int j = 0; j < childCount; j++) {
            View child = getChildAt(j);
            if (child.getVisibility() == GONE)
                continue;

            //在这里考虑margin
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            Log.d("onLayoutTag-" + i, lp.leftMargin + "");
            Log.d("onLayoutTag-" + i, lp.rightMargin + "");

            //在这里，对这4个参数做出说明：
            // l 左  控件左边线，距离父组件左边界的距离
            // t 上  控件上边线，距离父组件上边线的距离
            // r 右  控件右边线，距离父组件左边界的距离
            // b 下  控件下边线，距离父组件上边界的距离
            child.layout(offsetX + lp.leftMargin, 0, offsetX + child.getMeasuredWidth() - lp.rightMargin, child.getMeasuredHeight());
            // 设置每一个子控件的位置, 老规矩，第一个控件占满整个空间
            // 其他的控件，全部往右
            // X偏移量累加
            offsetX += child.getMeasuredWidth();
        }
    }

    float mLastTouchX;
    float mMinScrollDistance;
    float mScrollX;
    private static final int HOVER_TAP_SLOP = 10;//被认定为点击事件的范围标准值，在这个值构造的矩形“范围”之内，则认为是click，否则就可能是手势
    private static final int HOVER_TAP_TIMEOUT = 150;//点击的持续时间小于这个值，就被认定是click，大于等于，则被可能 滑动手势(说，可能，是要两个条件同时满足，才被认定是click)
    private MotionEvent mMoveDownEvent;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mMoveDownEvent = MotionEvent.obtain(event);//获得event,拷贝一份和原来的event一样的对象，避免对原对象造成影响
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                } else {
                    mVelocityTracker.clear();
                }
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_UP://当手指抬起
                Log.d("onTouchEvent", "手指抬起了" + event.getAction());
                int downX = (int) mMoveDownEvent.getRawX();
                int downY = (int) mMoveDownEvent.getY();
                //构建一个矩形，矩形的中心为down事件下的点，如果事件坐标在以按下时坐标为中心的宽度为 2 * HOVER_TAP_SLOP 的正方形内,则认为这个事件是点击事件
                //左上右下，构建一个矩形，如果up的事件点，在矩形区域之内，就认为是click，而不是scroll
                Rect rect = new Rect(downX - HOVER_TAP_SLOP, downY - HOVER_TAP_SLOP, downX + HOVER_TAP_SLOP, downY + HOVER_TAP_SLOP);
                //如何判定点是否在矩形之内,原来是contains方法
                boolean ifIntentToClick = rect.contains((int) event.getRawX(), (int) event.getY());
                //判定点击事件的时长小于设定的临界值
                boolean ifClickTooLong = event.getEventTime() - event.getDownTime() < HOVER_TAP_TIMEOUT;
                if (ifIntentToClick && ifClickTooLong) {//如果在范围之内,并且点击时长超过了临界值
                    // 就按照click处理
                    //现在来处理click，主布局，还有其他的隐藏组件。
                    handleClickEvent(event);
                } else {
                    mVelocityTracker.computeCurrentVelocity(1000);//当前速率?
                    float velocityX = mVelocityTracker.getXVelocity();
                    if (Math.abs(velocityX) > mViewConfiguration.getScaledMinimumFlingVelocity()) {
                        if (velocityX > 0) {
                            hideMenu();
                        } else {
                            showMenu();
                        }
                    } else {
                        toggleStatus();// 否则，就按照滑动事件处理
                    }
                    if (mVelocityTracker != null) {
                        mVelocityTracker.recycle();
                        mVelocityTracker = null;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE://在这里调用 菜单展开的函数
                if (ifAllowSwip) {
                    mVelocityTracker.addMovement(event);
                    updateScrollX(mLastTouchX - event.getRawX());
                    if (mScrollX == 0) {// 在平移距离为0时，允许父组件进行拦截
                        //允许父View进行事件拦截
                        getParent().requestDisallowInterceptTouchEvent(false);//事件可以外传
                    } else {//否则
                        //禁止父View进行事件拦截
                        getParent().requestDisallowInterceptTouchEvent(true);//事件不能外传，只能内部消化
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL://异常情况下会到这里
                Log.d("onTouchEvent", "手势异常取消了");
                toggleStatus();
                break;
        }
        mLastTouchX = event.getRawX();//记录上一次触摸事件的位置
        return true;//这里的返回值必须是true，这样，这个ViewGroup才能获得完整的触摸事件，否则，只会有down事件，其他的一概没有;  根据事件分发机制，它的子控件将touchEvent拦截了？
        //因为这里return true 的意思是，Touch事件不再往上上报。
        //或者，可以设置这个viewGroup的clickable属性为true,setClickable(true). 这是什么原因？还没搞懂。???????
        //没搞懂。这里先打个问号。我们继续。???????????????  TODO 后面再搞定
    }

    private void handleClickEvent(MotionEvent event) {
        // 首先，这里有多个子view，第一个是主布局，后面的都是隐藏view
        // 如何识别主布局和其他view呢？
        int childCount = getChildCount();
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            //判定当前event是不是点击在这个view范围之内
            if (ifClickInsideView(child, event)) {
                //如果此次事件，是作用在主布局上，而且，当前还是展开状态(展开状态的判定方式：当前滑动的距离是不是等于最大滑动距离)，那么这一次点击事件要被消费掉，用来收缩布局
                if (child == mCenterView && (mScrollX == mMaxScrollDistance)) {
                    hideMenu();
                } else {
                    if (i != 0) {//如果不是第一个view，（第一个控件是主布局）

                    }
                    // 至于真正的点击事件，我看还是要我来自定义
                    //算了，不用这个值判定了!ifScrolling,并不是在所有手机上都适用,ԅ(¯㉨¯ԅ)
//                    if (swipMenuClickListener != null && !ifScrolling) {
                    if (swipMenuClickListener != null) {
                        swipMenuClickListener.clickEvent(i);
                    } else {

                    }
                }
            }
        }
    }

    private boolean ifClickInsideView(View view, MotionEvent event) {//获取view的矩形范围
        Rect temp = new Rect();// 构造一个矩形
        view.getGlobalVisibleRect(temp);// 调用view的api，用view的实际可见绘制矩形区域 来重新定义这个矩形(注意，这里是相对于整个绘制坐标轴的矩形，而不是相对于父组件)
        return temp.contains((int) event.getRawX(), (int) event.getRawY());//由于上面用的是相对于整个坐标轴的矩形区域，
        // 这里要使用全局X,Y坐标，(getRawX和getRawY是全局的，而getX和getY是获取相对于父组件的坐标值)
    }


    /**
     * @param distance
     */
    private void updateScrollXImmediately(float distance) {//更新滚动距离值，并且平滑滚动一段距离
        float dx = mScrollX + distance;//目标X轴坐标值
        if (dx < mMinScrollDistance) {//矫正边界值
            dx = mMinScrollDistance;
            Log.d("updateScrollX2", "dx:" + dx);
        } else if (dx > mMaxScrollDistance) {
            dx = mMaxScrollDistance;
            Log.d("updateScrollX2", "2");
        }
        mScrollX = dx;//每平移一次，更新一次滑动的距离
        scrollTo((int) dx, 0); //粗暴的滚动
        Log.d("updateScrollX2", "mScrollX:" + mScrollX);
    }

    /**
     * @param distance
     */
    private void updateScrollX(float distance) {//更新滚动距离值，并且平滑滚动一段距离
        float dx = mScrollX + distance;//目标X轴坐标值
        if (dx < mMinScrollDistance) {//矫正边界值
            dx = mMinScrollDistance;
            Log.d("updateScrollX", "dx:" + dx);
        } else if (dx > mMaxScrollDistance) {
            dx = mMaxScrollDistance;
            Log.d("updateScrollX", "2");
        }
        mScrollX = dx;//每平移一次，更新一次滑动的距离
        Log.d("updateScrollX", "mScrollX:" + mScrollX);
        smoothScrollTo((int) dx, 0);//平滑柔和的滚动
    }

    private void toggleStatus() {//切换开和关的状态
        int scrollThreshold = getMeasuredHeight();//控件的高度，其实这个值也就是隐藏Menu的宽度
        if (mScrollX < scrollThreshold) {// 如果滑动的距离不足
            hideMenu();//就强制隐藏menu
        } else if (mScrollX > scrollThreshold) {// 如果滑动距离超出
            showMenu();//就显示Menu
        }
    }

    public static final int STATUS_NORMAL = 0;//预设状态：正常，也就是菜单收缩起来的状态
    public static final int STATUS_EXPAND = 1;//预设状态，展开状态
    public int mStatus = STATUS_NORMAL;// 默认是正常状态

    /**
     * 显示菜单
     */
    private void showMenu() {
        mStatus = STATUS_EXPAND;
        updateScrollX(mMaxScrollDistance);//展开menu，其实就是将viewGroup的内容往左平移，直至所有menu显示
    }

    /**
     * 隐藏菜单
     */
    public void hideMenu() {
        mStatus = STATUS_NORMAL;
        if (mScrollX > 0) {
            Log.d("computeScroll", "滑动开始");
            Log.d("hideMenu", "mScrollX:" + mScrollX);
            updateScrollX(-mMaxScrollDistance);// 收缩menu，其实也就将viewGroup的内容往右平移，直至所有menu隐藏
        }
    }

    /**
     * 每次调用invalidate，都会触发这个方法
     */
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            ifScrolling = true;
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        } else {// 这里的滑动结束的判定，在华为手机 HUAWEI CAZ-AL 10 上居然无效？！汝妹哦！
            //滑动结束？
            ifScrolling = false;
            Log.d("computeScroll", "滑动结束");
        }
        super.computeScroll();
    }

    /**
     * 平滑滚动
     *
     * @param fx
     * @param fy
     */
    private void smoothScrollTo(int fx, int fy) {
        int dx = fx - mScroller.getFinalX();
        int dy = fy - mScroller.getFinalY();
        smoothScrollBy(dx, dy);
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
        invalidate();
    }

    private SwipMenuClickListener swipMenuClickListener;

    public void setSwipMenuClickListener(SwipMenuClickListener swipMenuClickListener) {
        this.swipMenuClickListener = swipMenuClickListener;
    }

    public interface SwipMenuClickListener {
        void clickEvent(int index);
    }

}
